/*Scala code license:

 Copyright 2009/10 Osman Pub

 Licensed under the Apache License, Version 2.0 (the "License") you may not use
 this file except in compliance with the License.

 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software distributed
 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.

 */
/**
 Orginal java code credits:
 * @author Pepijn Van Eeckhoudt
 */
package demos

import common._
import java.awt.event._
import javax.media.opengl._
import javax.media.opengl.glu.GLU
import javax.swing._

object Lesson20 {
  def main(args: Array[String]) {
    val glDisplay = GLDisplay("Lesson 20: Masking", false)
    glDisplay addGLEventListener(new Renderer)
    glDisplay addKeyListener(new KeyAdapter {
      override def keyReleased(e: KeyEvent) =
        e getKeyCode match {
          case KeyEvent.VK_ESCAPE => glDisplay stop
          case KeyEvent.VK_M => response = Action masking
          case KeyEvent.VK_SPACE => response = Action scene
          case _ => response = Action none
        }
    })
    glDisplay start
  }

  object Action extends Enumeration { val masking, none, scene, stop = Value }
  private var response = Action none

  private class Renderer extends GLEventListener {
    /** Called by the drawable to initiate OpenGL rendering by the client.
     * After all GLEventListeners have been notified of a display event, the
     * drawable will swap its buffers if necessary.
     * @param gLDrawable The GLAutoDrawable object.
     */
    private val glu = new GLU
    private val textures = new Array[Int](5)

    private var masking, scene = true
    private var roll: Float = 0

    def display(glDrawable: GLAutoDrawable) {
      def update {
        response match {
          case Action.masking => masking = !masking
          case Action.scene=> scene = !scene
          case Action.scene=> scene = !scene
          case _ =>
        }
        response = Action none
      }

      val gl = glDrawable.getGL
      gl glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

      gl glLoadIdentity;									// Reset The View;
      gl glTranslatef(0, 0, -2)						// Move Into The Screen 5 Units
      gl glBindTexture(GL GL_TEXTURE_2D, textures(0))

      gl glBegin(GL GL_QUADS)
      gl glTexCoord2f(0, -roll + 0)
      gl glVertex3f(-1.1f, -1.1f, 0)	// Bottom Left
      gl glTexCoord2f(3, -roll + 0)
      gl glVertex3f(1.1f, -1.1f, 0)	// Bottom Right
      gl glTexCoord2f(3, -roll + 3)
      gl glVertex3f(1.1f, 1.1f, 0)	// Top Right
      gl glTexCoord2f(0, -roll + 3)
      gl glVertex3f(-1.1f, 1.1f, 0)	// Top Left
      gl glEnd											// Done Drawing The Quad

      gl glEnable(GL GL_BLEND)									// Enable Blending
      gl glDisable(GL GL_DEPTH_TEST)							// Disable Depth Testing

      if(masking) gl glBlendFunc(GL GL_DST_COLOR, GL GL_ZERO)				// Blend Screen Color With Zero (Black)

      scene match {
        case true =>
          gl glTranslatef(0, 0, -1)					// Translate Into The Screen One Unit
          gl glRotatef(roll * 360, 0, 0, 1)				// Rotate On The Z Axis 360 Degrees.

          if(masking) {
            gl glBindTexture(GL GL_TEXTURE_2D, textures(3))	// Select The Second Mask Texture
            gl glBegin(GL GL_QUADS)							// Start Drawing A Textured Quad
            gl glTexCoord2f(0, 0)
            gl glVertex3f(-1.1f, -1.1f, 0)	// Bottom Left
            gl glTexCoord2f(1, 0)
            gl glVertex3f(1.1f, -1.1f, 0)	// Bottom Right
            gl glTexCoord2f(1, 1)
            gl glVertex3f(1.1f, 1.1f, 0)	// Top Right
            gl glTexCoord2f(0, 1)
            gl glVertex3f(-1.1f, 1.1f, 0)	// Top Left
            gl glEnd									// Done Drawing The Quad
          }

          gl glBlendFunc(GL GL_ONE, GL GL_ONE)					// Copy Image 2 Color To The Screen
          gl glBindTexture(GL GL_TEXTURE_2D, textures(4))		// Select The Second Image Texture
          gl glBegin(GL GL_QUADS)								// Start Drawing A Textured Quad
          gl glTexCoord2f(0, 0)
          gl glVertex3f(-1.1f, -1.1f, 0)	// Bottom Left
          gl glTexCoord2f(1, 0)
          gl glVertex3f(1.1f, -1.1f, 0)	// Bottom Right
          gl glTexCoord2f(1, 1)
          gl glVertex3f(1.1f, 1.1f, 0)	// Top Right
          gl glTexCoord2f(0, 1)
          gl glVertex3f(-1.1f, 1.1f, 0)	// Top Left
          gl glEnd										// Done Drawing The Quad

        case false =>
          if(masking) {
            gl glBindTexture(GL GL_TEXTURE_2D, textures(1))	// Select The First Mask Texture
            gl glBegin(GL GL_QUADS)							// Start Drawing A Textured Quad
            gl glTexCoord2f(roll + 0, 0)
            gl glVertex3f(-1.1f, -1.1f, 0)	// Bottom Left
            gl glTexCoord2f(roll + 4, 0)
            gl glVertex3f(1.1f, -1.1f, 0)	// Bottom Right
            gl glTexCoord2f(roll + 4, 4)
            gl glVertex3f(1.1f, 1.1f, 0)	// Top Right
            gl glTexCoord2f(roll + 0, 4)
            gl glVertex3f(-1.1f, 1.1f, 0)	// Top Left
            gl glEnd									// Done Drawing The Quad
          }

          gl glBlendFunc(GL GL_ONE, GL GL_ONE)					// Copy Image 1 Color To The Screen
          gl glBindTexture(GL GL_TEXTURE_2D, textures(2))		// Select The First Image Texture
          gl glBegin(GL GL_QUADS)								// Start Drawing A Textured Quad
          gl glTexCoord2f(roll + 0, 0)
          gl glVertex3f(-1.1f, -1.1f, 0)	// Bottom Left
          gl glTexCoord2f(roll + 4, 0)
          gl glVertex3f(1.1f, -1.1f, 0)	// Bottom Right
          gl glTexCoord2f(roll + 4, 4)
          gl glVertex3f(1.1f, 1.1f, 0)	// Top Right
          gl glTexCoord2f(roll + 0, 4)
          gl glVertex3f(-1.1f, 1.1f, 0)	// Top Left
          gl glEnd										// Done Drawing The Quad

        case _ =>
      }
        
      gl glEnable(GL GL_DEPTH_TEST)							// Enable Depth Testing
      gl glDisable(GL GL_BLEND)								// Disable Blending

      roll += 0.002f										// Increase Our Texture Roll Variable
      if(roll > 1) roll -= 1										// Subtract 1 From Roll
      if(response != Action.none) update
    }

    /** Called when the display mode has been changed.  <B>!!
     * CURRENTLY UNIMPLEMENTED IN JOGL !!</B>
     * @param gLDrawable The GLAutoDrawable object.
     * @param modeChanged Indicates if the video mode has changed.
     * @param deviceChanged Indicates if the video device has changed.
     */
    def displayChanged(glDrawable: GLAutoDrawable, modeChanged: Boolean,
                       deviceChanged: Boolean) {}

    /** Called by the drawable immediately after the OpenGL context is
     * initialized for the first time. Can be used to perform one-time OpenGL
     * initialization such as setup of lights and display lists.
     * @param gLDrawable The GLAutoDrawable object.
     */
    def init(glDrawable: GLAutoDrawable) {
      val gl = glDrawable.getGL

      def loadGLTextures {
        val tileNames = Array("demos/data/images/logo.png",
          "demos/data/images/mask1.png", "demos/data/images/image1.png",
         "demos/data/images/mask2.png", "demos/data/images/image2.png"
        )
        gl glGenTextures(5, textures, 0)
        for(i <- 0 until 5) {
          val textImg = TextureReader readTexture(tileNames(i))
          gl glBindTexture(GL GL_TEXTURE_2D, textures(i))
          gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MIN_FILTER,
                             GL GL_LINEAR)
          gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MAG_FILTER,
                             GL GL_LINEAR)
          gl glTexImage2D(GL GL_TEXTURE_2D, 0, GL GL_RGB, textImg.width,
                          textImg.height, 0,
          GL GL_RGB, GL GL_UNSIGNED_BYTE, textImg.pixels)
        }
      }

      // init
      loadGLTextures
      
      gl glShadeModel(GL GL_SMOOTH)               // Enable Smooth Shading
      gl glClearColor(0, 0, 0, 0)     // Black Background
      gl glClearDepth(1)                       // Depth Buffer Setup
      gl glEnable(GL GL_DEPTH_TEST) 							// Enables Depth Testing
      gl glDepthFunc(GL GL_LEQUAL) 						// The Type Of Depth Testing To Do
      gl glHint(GL GL_PERSPECTIVE_CORRECTION_HINT, GL GL_NICEST) 	
      gl glEnable(GL GL_TEXTURE_2D)
    }

    /** Called by the drawable during the first repaint after the component has
     * been resized. The client can update the viewport and view volume of the
     * window appropriately, for example by a call to
     * GL.glViewport(int, int, int, int) note that for convenience the component
     * has already called GL.glViewport(int, int, int, int)(x, y, width, height)
     * when this method is called, so the client may not have to do anything in
     * this method.
     * @param gLDrawable The GLAutoDrawable object.
     * @param x The X Coordinate of the viewport rectangle.
     * @param y The Y coordinate of the viewport rectanble.
     * @param width The new width of the window.
     * @param height The new height of the window.
     */
    def reshape(glDrawable: GLAutoDrawable, x: Int, y: Int, width: Int,
                height: Int) {
      require(height != 0)

      val gl = glDrawable.getGL
      val h = width.asInstanceOf[Float] / height.asInstanceOf[Float]
      
      gl glViewport(0, 0, width, height)
      gl glMatrixMode(GL GL_PROJECTION)
      gl glLoadIdentity;
      glu gluPerspective(45, h, 1, 1000)
      gl glMatrixMode(GL GL_MODELVIEW)
      gl glLoadIdentity
    }
  }
}